﻿using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Data.Linq;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using Microsoft.WindowsAzure.ServiceRuntime;
using Pkcs12ProtectedConfigurationProvider;
using ScheduleParserWorkerRole;

namespace RecorderWorkerRole
{
    public class RecorderWorkerRole : RoleEntryPoint
    {
        private FastEncryptionWorkaround _encryptor;
        private string _connectionString;
        private SqlConnectionStringBuilder _csBuilder;

        private StationsDataModelDataContext _context;
        private readonly DownloadersRegistry _downloadersRegistry = new DownloadersRegistry();
        private bool _keepRunningFlag = true;

        public override void Run()
        {
            // This is a sample worker implementation. Replace with your logic.
            Trace.TraceInformation("RecorderWorkerRole entry point called");

            while (_keepRunningFlag)
            {
                try
                {
                    MainCycle();
                }
                catch (Exception e)
                {
                    Trace.TraceError("ScheduleParserWorkerRole MainCycle - failed: " + e);
                    Thread.Sleep(15000);
                }
            }

        }

        static public AutoResetEvent MainCycleSynchronization = new AutoResetEvent(false);

        private void MainCycle()
        {
            StartAllPendingDownloadsAsync();
            RevisitAllActiveDownloads();
            var inactivityTimeout = new TimeSpan[]{_downloadersRegistry.GetInactivityTimeout(), GetInactivityTimeout()}.Min();
            Trace.TraceInformation("sleeping for {0} until {1}", inactivityTimeout, DateTime.UtcNow+inactivityTimeout);
            MainCycleSynchronization.WaitOne(inactivityTimeout);
        }

        private void RevisitAllActiveDownloads()
        {
            foreach (var iterDownloader in _downloadersRegistry.GetActiveDownloads())
            {
                ProcessDownloader(iterDownloader);
            }
        }

        private void StartAllPendingDownloadsAsync()
        {
            var aRightBoundary = DateTime.UtcNow.AddSeconds(15);
            var aLeftBoundary = DateTime.UtcNow.AddSeconds(-1);
            var aDownloadList = PendingDownloads(aRightBoundary, aLeftBoundary).ToList();

            foreach (var iterShow
                in aDownloadList)
            {
                ProcessDownloader(_downloadersRegistry.GetDownloader(iterShow));
            }
        }

        private void ProcessDownloader(Downloader iDownloader)
        {
            iDownloader.StateExpirationTime = DateTime.UtcNow.AddYears(20);
            switch (iDownloader.State)
            {
                case Downloader.EDownloadStates.ConnectionRequested:
                    iDownloader.ConnectionRequestTimeoutAction();
                    break;
                case Downloader.EDownloadStates.PendingDownload:
                    iDownloader.Start(_context);
                    break;
                case Downloader.EDownloadStates.DownloadCompleted:
                    MarkDownloadComplete(iDownloader);
                    Finish(iDownloader);
                    break;
                case Downloader.EDownloadStates.DownloadError:
                    Finish(iDownloader);
                    break;
                case Downloader.EDownloadStates.DownloadStarted:
                    iDownloader.UpdateStateExpirationTime();
                    //Do some sanity checks here, for example, check if current time is in the show's time window
                    break;
            }

            //Sanity check - detect stale downloaders
            if (iDownloader.State != Downloader.EDownloadStates.DownloadFinishedForgetMe
                && iDownloader.CurrentDownloadInfo != null 
                && iDownloader.CurrentDownloadInfo.StopTime.AddHours(2) < DateTime.UtcNow)
            {
                Trace.TraceInformation("SANITY: force download state to finished {0}, {1} (should have been finished at {2})",
                    iDownloader.CurrentDownloadInfo.ShowId, DateTime.UtcNow, iDownloader.CurrentDownloadInfo.StopTime);
                Finish(iDownloader);
            }
        }

        private void Finish(Downloader iDownloader)
        {
            Trace.TraceInformation("FINISH: {0}, {1}", iDownloader.CurrentDownloadInfo, DateTime.UtcNow);
            iDownloader.Dispose();
            iDownloader.Cleanup();
        }

        private void MarkDownloadComplete(Downloader iDownloader)
        {
            if (RecordingsBlobStorage.IsStaging())
            {
                Trace.TraceInformation("Show is not marked since we are in staging phase, ignoring {0}", iDownloader.BlobUri);
                return;
            }
            if (iDownloader != null && iDownloader.CurrentDownloadInfo != null)
            {
                var id = iDownloader.CurrentDownloadInfo.ShowId;
                foreach (var iterShow in _context.schedules.Where(item => item.Id == id))
                {
                    _context.Refresh(RefreshMode.OverwriteCurrentValues, iterShow);
                    if (iterShow.RecordedUri == null)
                    {
                        iterShow.RecordedUri = iDownloader.BlobUri;
                        Trace.TraceInformation("Marked as complete {0}", iDownloader.BlobUri);
                    }
                    else
                    {
                        Trace.TraceInformation("Show is already marked as complete, ignoring {0}", iDownloader.BlobUri);
                    }
                }
                _context.SubmitChanges(ConflictMode.ContinueOnConflict);
            }
        }

        private IEnumerable<schedule> PendingDownloads(DateTime aRightBoundary, DateTime aLeftBoundary)
        {
            return _context.
                schedules.
                Where(schedule => schedule.StartTime <= aRightBoundary).
                Where(schedule => schedule.StopTime >= DateTime.UtcNow). //this condition is virtually not efficient
                Where(schedule => schedule.StartTime >= aLeftBoundary).
                OrderByDescending(schedule => schedule.StopTime);
        }

        private TimeSpan GetInactivityTimeout()
        {
            var nextShow = GetNearestStartTime().AddSeconds(-14);
            var now = DateTime.UtcNow;
            if (now < nextShow)
            {
                return nextShow - now;
            }
            else
            {
                return TimeSpan.FromSeconds(2);
            }
            
        }

        private DateTime GetNearestStartTime()
        {
            var rightBound = DateTime.UtcNow.AddMinutes(5);
            var now = DateTime.UtcNow;
            var nearestStartTime = rightBound;
            var nearestShows = _context.
                schedules.
                Where(item => item.StartTime > now).
                Where(item => item.StartTime < rightBound).
                OrderBy(item => item.StartTime);
            foreach (var iterShow in nearestShows)
            {
                if (!_downloadersRegistry.Exists(iterShow.Id))
                {
                    nearestStartTime = iterShow.StartTime;
                }
            }
            return nearestStartTime;
        }

        public override bool OnStart()
        {
            RecordingsBlobStorage.Init();
            // Set the maximum number of concurrent connections 
            ServicePointManager.DefaultConnectionLimit = 72;

            // For information on handling configuration changes
            // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.
            _context = CreateDbContext();
            return base.OnStart();
        }

        private StationsDataModelDataContext CreateDbContext()
        {
            Trace.TraceInformation("ScheduleParserWorkerRole onStart - creating DB connection");
            _encryptor = new FastEncryptionWorkaround
            {
                Thumbprint = RoleEnvironment.GetConfigurationSettingValue("DecryptionCert")
            };
            _connectionString = _encryptor.Decrypt(RoleEnvironment.GetConfigurationSettingValue("DatabaseConnectionString"));
            Trace.TraceInformation("ScheduleParserWorkerRole onStart - creating connection");
            _csBuilder = new SqlConnectionStringBuilder(_connectionString);
            return new StationsDataModelDataContext(_csBuilder.ConnectionString);
        }

        public override void OnStop()
        {
            _keepRunningFlag = false;
            base.OnStop();
        }
    }
}
